Manejo de excepciones en Java. Parte 1

En esta serie de posts pretendo explicar diferentes conceptos del manejo de excepciones en Java, algunos son bastante sencillos, otros un poco más complejos. Se mostrarán varios ejemplos en código y definición de algunos conceptos relacionados al tema.

Comencemos con lo básico. Los bloques try/catch sirven para el manejo de excepciones en la ejecución de un programa en Java. Un ejemplo muy sencillo del manejo de try/catch seria mas o menos así:

try{

  // Código que podría lanzar una excepción

}catch(SomeException e){

  // Hacer algo cuando exista una excepción de tipo SomeException dentro de try

}

Creo que se entiende muy bien lo anterior. Dentro de try pones el código que podría lanzar una excepción, y si ocurre una excepción de tipo SomeException (es un tipo de excepción que me acabo de inventar, solo para el ejemplo) la ejecución del código se detiene dentro de try y se brinca directamente hacia el bloque catch, mandando un parámetro llamado e que es de tipo SomeException, para con ello darle el tratamiento adecuado al programa en base a la excepción que acaba de ocurrir. Básicamente, con esto evitas que tu programa se muera irremediablemente y termine abruptamente si ocurre una excepción, permitiéndote hacer algo como mandarle al usuario un mensaje amigable de que algo salió mal, o incluso hacer algo para mitigar la excepción.

En caso de que no lo supieras, es posible tener mas de un bloque catch para un solo try (incluso tener cero bloques catch para un solo try, eso lo explicaré en otro post más adelante), aquí un ejemplo:

try{

  // Código que podría lanzar una excepción

}catch(SomeException e){

  // Hacer algo cuando exista una excepción de tipo SomeException dentro de try

}catch(SomeOtherException e){

  // Hacer algo cuando exista una excepción de tipo SomeOtherException dentro de try

}

¿Y para que tener mas de un catch? En caso de que quisieras manejar de manera diferente la situación dependiendo del tipo de excepción que ocurra, puedes tener múltiples catchs. Veamos un ejemplo con código real:

import java.util.InputMismatchException;
import java.util.Scanner;

public class Division {
    public static void main(String[] args) {
        try{
            Scanner sc = new Scanner(System.in);
            int a = sc.nextInt();
            int b = sc.nextInt();

            System.out.println("Resultado = " + (a/b));
        }
        catch(InputMismatchException e){
            System.out.println("No ingresaste un número válido. Bye!");
        }
        catch(ArithmeticException e){
            System.out.println("Una excepción aritmética a ocurrido. Bye!");
        }
    }
}

En este pequeño ejemplo se le pide al usuario dos números enteros, luego se imprime en pantalla la división de esos dos números enteros que el usuario ingresó. En este programita cosas terribles pueden ocurrir. Si el usuario ingresa algo que no sea un número entero ocurrirá una excepción y se ejecutará el código que esta dentro de catch(InputMismatchException e). Si el usuario ingresa números enteros correctos pero lo que ingresa provoca una división entre cero, ocurrirá una excepción (por obvias razones) y se ejecutará el código que esta dentro de catch(ArithmeticException e).

Hay que tomar en cuenta algo muy importante al manejar múltiples catch, el cual es que las excepciones mas especializadas jerarquicamente hablando deben estar antes que las excepciones mas generales jerarquicamente hablando, de lo contrario podríamos crear un unreachable code y por consiguiente causará un error en tiempo de compilación. ¿Que diablos me refiero con todo esto que acabo de decir? Veamos un ejemplo. Al código anterior le vamos a agregar un catch mas, quedando de la siguiente manera:

import java.util.InputMismatchException;
import java.util.Scanner;
 
public class Division {
    public static void main(String[] args) {
        try{
            Scanner sc = new Scanner(System.in);
            int a = sc.nextInt();
            int b = sc.nextInt();
 
            System.out.println("Resultado = " + (a/b));
        }
        catch(InputMismatchException e){
            System.out.println("No ingresaste un número válido. Bye!");
        }
        catch(ArithmeticException e){
            System.out.println("Una excepción aritmética a ocurrido. Bye!");
        }
        catch(Exception e){
            System.out.println("Ocurrió un error desconocido");
        }
    }
}

Básicamente si ocurre una excepción que no sea InputMismatchException o ArithmeticException, el flujo de ejecución caerá dentro de catch(Exception e). Exception se encuentra en el tope de la herencia de las excepciones, todas las excepciones heredan de Exception (existe algo aun mas arriba de Exception llamado Throwable, pero queda fuera del scope de este post). Hasta ahora todo esta bien, pero ¿Que pasa si cambio un poco el orden en que aparecen los catchs? Veamos un ejemplo:

import java.util.InputMismatchException;
import java.util.Scanner;
 
public class Division {
    public static void main(String[] args) {
        try{
            Scanner sc = new Scanner(System.in);
            int a = sc.nextInt();
            int b = sc.nextInt();
 
            System.out.println("Resultado = " + (a/b));
        }
        catch(Exception e){
            System.out.println("Ocurrió un error desconocido");
        }
        catch(InputMismatchException e){
            System.out.println("No ingresaste un número válido. Bye!");
        }
        catch(ArithmeticException e){
            System.out.println("Una excepción aritmética a ocurrido. Bye!");
        }
    }
}

Como podemos ver se ha puesto primero el catch(Exception e) antes que los demás catchs, esto provocará un error en tiempo de compilación y tu programa no podrá correr. ¿Por qué?. Cuando ocurre una excepción dentro de try se pasa el control del programa a uno de los bloques catch, el sistema debe evaluar y decidir a cual catch se le debe pasar el control de la ejecución, revisando uno por uno. Como el catch que esta primero es catch(Exception e), y ya que de Exception heredan todas las excepciones, sería a ese bloque catch al cual se le pase el control del programa. Las excepciones de tipo InputMismatchException y de ArithmeticException que ocurran dentro de try siempre serán “cachadas” dentro de catch(Exception e) y nunca dentro de catch(InputMismatchException e) ni de catch(ArithmeticException e), acuerdate que de Exception heredan todas las excepciones. El compilador se da cuenta que los catchs catch(InputMismatchException e) y catch(ArithmeticException e) se convierten en unreachable code (código que jamás será ejecutado sin importar que pase) y pues como en Java cualquier unreachable code provoca un error en tiempo de compilación, el programa no funcionará. Se tendría que poner al final el catch mas general (en este caso catch(Exception e)) para que el programa ya pueda funcionar. ¿Interesante no?

Una cosa mas antes de terminar este primer post sobre el manejo de excepciones en Java. ¿Que tal si el código para manejar dos tipos de excepciones es exactamente el mismo? Pues tendríamos dos catchs con el mismo código cada uno, osea, tendríamos código repetido. Afortunadamente a partir de Java 7 se agregó una nueva feature que nos ayuda a resolver esto de manera muy cute, pero eso lo dejaremos para la segunda parte de esta serie de posts de manejo de excepciones en Java la cual puedes consultar aquí.

¡Saluditos a todos!

Entradas relacionadas

Dejar un Comentario